package com.javabi.common.concurrent.collect.copyonwrite;

import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;

import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.google.common.collect.ForwardingSet;

public abstract class CopyOnWriteSet<E> extends ForwardingSet<E> {

	private transient final Lock lock = new ReentrantLock();
	private volatile Set<E> delegate = emptySet();

	@Override
	protected Set<E> delegate() {
		return delegate;
	}

	@Override
	public Iterator<E> iterator() {
		return unmodifiableSet(this.delegate).iterator();
	}

	@Override
	public void clear() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			this.delegate = emptySet();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean add(E element) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Set<E> newSet = newSet(this.delegate);
			boolean added = newSet.add(element);
			this.delegate = newSet;
			return added;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean addAll(Collection<? extends E> addCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Set<E> newSet = newSet(this.delegate);
			boolean added = newSet.addAll(addCollection);
			this.delegate = newSet;
			return added;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean remove(Object element) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Set<E> newSet = newSet(this.delegate);
			boolean removed = newSet.remove(element);
			this.delegate = newSet;
			return removed;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean removeAll(Collection<?> removeCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Set<E> newSet = newSet(this.delegate);
			boolean removed = newSet.removeAll(removeCollection);
			this.delegate = newSet;
			return removed;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean retainAll(Collection<?> retainCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Set<E> newSet = newSet(this.delegate);
			boolean removed = newSet.retainAll(retainCollection);
			this.delegate = newSet;
			return removed;
		} finally {
			lock.unlock();
		}
	}

	protected abstract Set<E> newSet(Set<E> set);

}
